#include "Evaluator.hpp"

#include "Binding/BoundBinary.hpp"
#include "Binding/BoundBlock.hpp"
#include "Binding/BoundConditionalGotoStatement.hpp"
#include "Binding/BoundExpressionStatement.hpp"
#include "Binding/BoundGotoStatement.hpp"
#include "Binding/BoundLabelStatement.hpp"
#include "Binding/BoundLiteral.hpp"
#include "Binding/BoundUnary.hpp"

#include "Symbol/TypeSymbol.hpp"
#include "Syntax/Kind.hpp"
#include "Syntax/Object/Boolean.hpp"
#include "Syntax/Object/Integer.hpp"
#include "Syntax/Object/Object.hpp"
#include "Syntax/Object/String.hpp"

#include <cstddef>
#include <format>
#include <memory>
#include <string>
#include <vector>

SharedObject Evaluator::Evaluate()
{
    std::map<SharedBoundLabel, int> labelToIndex;

    for (auto i = 0; i < this->Root->Statements.size(); i++)
    {
        auto statement = this->Root->Statements[i];
        if (statement->GetKind() == BoundNodeKind::LabelStatement)
        {
            auto label                 = (BoundLabelStatement *)statement.get();

            labelToIndex[label->Label] = i + 1;
        }
    }
    size_t index = 0;

    while (index < this->Root->Statements.size())
    {
        auto node = this->Root->Statements[index];

        switch (node->GetKind())
        {
        case BoundNodeKind::ExpressionStatement :
            this->EvaluateExpressionStatement((BoundExpressionStatement *)node.get());
            index++;
            break;
        case BoundNodeKind::VariableDeclaration :
            this->EvaluateVariableDeclaration((BoundVariableDeclaration *)node.get());
            index++;
            break;
        case BoundNodeKind::GotoStatement :
        {
            auto gs = (BoundGotoStatement *)node.get();
            index   = labelToIndex[gs->Label];
            break;
        }
        case BoundNodeKind::ConditionalGotoStatement :
        {
            auto cgs       = (BoundConditionalGotoStatement *)node.get();

            auto condition = this->EvaluateExpression(cgs->Condtion);

            if (condition->GetType() == TypeKind::Bool)
            {
                auto cond = (Boolean *)condition.get();
                if (cond->Value == cgs->JumpIfTrue)
                {
                    index = labelToIndex[cgs->Lable];
                    break;
                }
            }

            index++;
        }
        break;
        case BoundNodeKind::LabelStatement :
            index++;
            break;
        default :
            throw "Unexpected node" + std::to_string((int)node->GetKind());
        }
    }
    return this->LastValue;
}
void Evaluator::EvaluateVariableDeclaration(BoundVariableDeclaration *variable)
{
    auto value = this->EvaluateExpression(variable->Initializer);

    this->Variables->assignValue(variable->Variable, value);

    this->LastValue = value;
}

void Evaluator::EvaluateExpressionStatement(BoundExpressionStatement *statement)
{
    this->LastValue = this->EvaluateExpression(statement->Expression);
}

SharedObject Evaluator::EvaluateExpression(SharedBoundExpression node)
{
    if (node == nullptr)
        throw "Unexpected node is nullptr";

    switch (node->GetKind())
    {
    case BoundNodeKind::LiteralExpression :
        return this->EvaluateLiteralExpression((BoundLiteralExpression *)node.get());
    case BoundNodeKind::VariableExpression :
        return this->EvaluateVariableExpression((BoundVariableExpression *)node.get());
    case BoundNodeKind::AssignmentExpression :
        return this->EvaluateAssignmentExpression((BoundAssignmentExpression *)node.get());
    case BoundNodeKind::UnaryExpression :
        return this->EvaluateUnaryExpression((BoundUnaryExpression *)node.get());
    case BoundNodeKind::BinaryExpression :
        return this->EvaluateBinaryExpression((BoundBinaryExpression *)node.get());

    default :
        throw "Unexpected node" + std::to_string((int)node->GetKind());
    }
}

SharedObject Evaluator::EvaluateLiteralExpression(BoundLiteralExpression *literal)
{
    return literal->Value;
}
SharedObject Evaluator::EvaluateVariableExpression(BoundVariableExpression *variable)
{
    auto         var   = variable->Variable;
    SharedObject value = this->Variables->GetVarialeValue(var->GetName());
    if (value == nullptr)
        throw std::format("EvaluateExpression:[Kind == VariableExpression] {} not found. ", var->GetName());

    // std::cout <<  "EvaluateExpression[Variable]:"<< variable->GetName() << "= "<< value->ToString() << std::endl;
    return value;
}
SharedObject Evaluator::EvaluateAssignmentExpression(BoundAssignmentExpression *assignment)
{
    auto value = this->EvaluateExpression(assignment->Expression);

    this->Variables->assignValue(assignment->Variable, value);
    // std::cout <<  "EvaluateExpression[Assignment]:"<< value->ToString() << std::endl;
    return value;
}
SharedObject Evaluator::EvaluateUnaryExpression(BoundUnaryExpression *unary)
{
    auto operand = this->EvaluateExpression(unary->Operand);
    switch (unary->Op->Kind)
    {
    case BoundUnaryOperatorKind::Identity :
        return operand;
    case BoundUnaryOperatorKind::Negation :
    {
        Integer *value = (Integer *)operand.get();
        return std::make_shared<Integer>(-value->Value);
    }
    case BoundUnaryOperatorKind::LogicalNegation :
    {
        Boolean *value = (Boolean *)operand.get();
        return std::make_shared<Boolean>(!value->Value);
    }
    case BoundUnaryOperatorKind::OnesComplement :
    {
        Integer *value = (Integer *)operand.get();
        return std::make_shared<Integer>(~value->Value);
    }
    default :
        throw "Unexpected unary operator : " + std::to_string((int)unary->Op->Kind);
    }
}
SharedObject Evaluator::EvaluateBinaryExpression(BoundBinaryExpression *binary)
{
    auto left  = this->EvaluateExpression(binary->Left);
    auto right = this->EvaluateExpression(binary->Right);

    switch (binary->Op->Kind)
    {
    case BoundBinaryOperatorKind::Addition :
    {
        if (binary->GetType()->eq(TypeSymbol::GetInt()))
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Integer>(x->Value + y->Value);
        }
        else
        {
            String *x = (String *)left.get();
            String *y = (String *)right.get();
            return std::make_shared<String>(x->Value + y->Value);
        }
    }
    case BoundBinaryOperatorKind::Subtraction :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Integer>(x->Value - y->Value);
    }
    case BoundBinaryOperatorKind::Multiplication :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Integer>(x->Value * y->Value);
    }
    case BoundBinaryOperatorKind::Division :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Integer>(x->Value / y->Value);
    }
    case BoundBinaryOperatorKind::BitwiseAnd :
    {
        if (left->GetType() == TypeKind::Int)
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Integer>(x->Value & y->Value);
        }
        if (left->GetType() == TypeKind::Bool)
        {
            Boolean *x = (Boolean *)left.get();
            Boolean *y = (Boolean *)right.get();
            return std::make_shared<Integer>(x->Value & y->Value);
        }
    }
    case BoundBinaryOperatorKind::BitwiseOR :
    {
        if (left->GetType() == TypeKind::Int)
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Integer>(x->Value | y->Value);
        }
        if (left->GetType() == TypeKind::Bool)
        {
            Boolean *x = (Boolean *)left.get();
            Boolean *y = (Boolean *)right.get();
            return std::make_shared<Integer>(x->Value | y->Value);
        }
    }
    case BoundBinaryOperatorKind::BitwiseXor :
    {
        if (left->GetType() == TypeKind::Int)
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Integer>(x->Value ^ y->Value);
        }
        if (left->GetType() == TypeKind::Bool)
        {
            Boolean *x = (Boolean *)left.get();
            Boolean *y = (Boolean *)right.get();
            return std::make_shared<Integer>(x->Value ^ y->Value);
        }
    }
    case BoundBinaryOperatorKind::Remainder :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Integer>(x->Value % y->Value);
    }
    case BoundBinaryOperatorKind::LogicalAnd :
    {
        Boolean *x = (Boolean *)left.get();
        Boolean *y = (Boolean *)right.get();
        return std::make_shared<Boolean>(x->Value && y->Value);
    }
    case BoundBinaryOperatorKind::LogicalOr :
    {
        Boolean *x = (Boolean *)left.get();
        Boolean *y = (Boolean *)right.get();
        return std::make_shared<Boolean>(x->Value || y->Value);
    }
    case BoundBinaryOperatorKind::Equals :
    {
        if ((left->GetType() == TypeKind::Int) && (right->GetType() == TypeKind::Int))
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Boolean>(x->Value == y->Value);
        }
        else if ((left->GetType() == TypeKind::Bool) && (right->GetType() == TypeKind::Bool))
        {
            Boolean *x = (Boolean *)left.get();
            Boolean *y = (Boolean *)right.get();
            return std::make_shared<Boolean>(x->Value == y->Value);
        }
        return std::make_shared<Integer>(0);
    }
    case BoundBinaryOperatorKind::NotEquals :
    {
        if ((left->GetType() == TypeKind::Int) && (right->GetType() == TypeKind::Int))
        {
            Integer *x = (Integer *)left.get();
            Integer *y = (Integer *)right.get();
            return std::make_shared<Boolean>(x->Value != y->Value);
        }
        else if ((left->GetType() == TypeKind::Bool) && (right->GetType() == TypeKind::Bool))
        {
            Boolean *x = (Boolean *)left.get();
            Boolean *y = (Boolean *)right.get();
            return std::make_shared<Boolean>(x->Value != y->Value);
        }
        return std::make_shared<Integer>(0);
    }
    case BoundBinaryOperatorKind::Less :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Boolean>(x->Value < y->Value);
    }
    case BoundBinaryOperatorKind::LessOrEquals :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Boolean>(x->Value <= y->Value);
    }
    case BoundBinaryOperatorKind::Greater :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Boolean>(x->Value > y->Value);
    }
    case BoundBinaryOperatorKind::GreaterOrEquals :
    {
        Integer *x = (Integer *)left.get();
        Integer *y = (Integer *)right.get();
        return std::make_shared<Boolean>(x->Value >= y->Value);
    }
    default :
        throw "Unexpected binary operator : " + std::to_string((int)binary->Op->Kind);
    }
}